/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	ComponentCamera.cpp
*
*	Comments	-	See ComponentCamera.h
*
**************************************************************************************/
#include "../Include/ComponentCamera.h"
#include "../Include/Engine.h"
#include "../Include/Renderer.h"
//#include "../Include/CameraRenderPathSettingsLPP.h"
#include "../Include/CameraRenderPathSettingsFP.h"
#include "../Include/GlobalGraphicsStrings.h"
#include "../Include/Actor.h"

#include "../Include/EventRender.h"
#include "../Include/EventTransform.h"

#include "../Include/RenderPathLightPrePass.h"
#include "../Include/RenderEntryBindParameters.h"
#include "../Include/ParameterBinderScalar.h"
#include "../Include/Scene.h"
#include "../Include/WindowView.h"

namespace Pulse
{
	ComponentCamera::ComponentCamera( Actor *pOwner )
		: IComponent( pOwner ), m_renderView( this )
	{
		m_pWorldTransform = PSX_NULL;

		m_bWindowViewExist = TRUE;

		m_pRenderer = PSX_NULL;
		m_pREBindParams = PSX_NULL;
		m_renderPath = PSX_NULL;
		m_pRenderPathSettings = PSX_NULL;

	}

	ComponentCamera::~ComponentCamera( void )
	{
		PSX_SafeDelete( m_pREBindParams );

		m_pOwner->RemoveListener( EEvent::RENDER, this, &ComponentCamera::OnRender );
		
		if ( m_bWindowViewExist )
		{
			m_pOwner->GetScene()->GetWindowViewOwner()->RemoveListener( EEvent::WINDOW_VIEW_RESIZE, this, &ComponentCamera::OnWindowViewResize );
			m_pOwner->GetScene()->GetWindowViewOwner()->RemoveListener( EEvent::WINDOW_VIEW_DESTROY, this, &ComponentCamera::OnWindowViewDestroy );
		}
	}

	EErrorCode::Type ComponentCamera::Initialize( XMLElementPtr pElem ) 
	{
		m_bActiveCamera = FALSE;
		m_bLocalizedRendering = FALSE;

		m_projType = EProjection::UNKNOWN;
		m_width = 0;
		m_height = 0;
		m_near = 0.1f;
		m_far = 100.0f;
		m_aspect = 800.0f / 600.0f;
		m_fov = PSX_PI / 4.0f;

		m_proj.BuildIdentity();
		m_pWorldTransform = m_pOwner->GetTransformWorld();
		m_view = Matrix4x4::IDENTITY;

		m_pRenderer = Engine::GetInstance()->GetRenderer();
		m_pREBindParams = new RenderEntryBindParameters;


		{
			ParameterSystem *pParamSys = Engine::GetInstance()->GetRenderer()->GetParameterSystem();
			ParameterScalar *pProjA = pParamSys->GetScalar( GFX_PARAM_CAM_PROJ_A );
			ParameterScalar *pProjB = pParamSys->GetScalar( GFX_PARAM_CAM_PROJ_B );


			m_pREBindParams->SetStage( ERenderViewStage::MAIN );
			m_pREBindParams->SetStageStep( ERenderViewStageStep::PRE_PROCESS );

			// Projection parameters
			IParameterBinder *pBinderA = m_pREBindParams->AddParameter( pProjA );
			IParameterBinder *pBinderB = m_pREBindParams->AddParameter( pProjB );

			// Projection
			IParameter *pParam;

			pParam = pParamSys->GetMatrix( GFX_PARAM_PROJECTION );
			m_pParamProj = (ParameterBinderMatrixRef*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetMatrix( GFX_PARAM_CAM_VIEW );
			m_pParamView = (ParameterBinderMatrixRef*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetMatrix( GFX_PARAM_CAM_VIEW_PROJ );
			m_pParamViewProj = (ParameterBinderMatrixRef*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetMatrix( GFX_PARAM_CAM_INV_VIEW_PROJ );
			m_pParamInvViewProj = (ParameterBinderMatrixRef*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetVector3( GFX_PARAM_CAM_POSWS );
			m_pParamPosWS = (ParameterBinderVector3*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetScalar( GFX_PARAM_CAM_CLIP_NEAR );
			m_pParamClipNear = (ParameterBinderScalar*)m_pREBindParams->AddParameter( pParam );
			pParam = pParamSys->GetScalar( GFX_PARAM_CAM_CLIP_FAR );
			m_pParamClipFar = (ParameterBinderScalar*)m_pREBindParams->AddParameter( pParam );

			m_pParamProj->SetAsMatrix( &m_proj );
			m_pParamView->SetAsMatrix( &m_view );
			m_pParamViewProj->SetAsMatrix( &m_viewProj );
			m_pParamInvViewProj->SetAsMatrix( &m_invViewProj );
			m_pParamPosWS->SetAsVector3( &GetOwnerUnsafe()->GetTransformWorld()->GetTranslation() );

		}

		// Register events
		m_pOwner->AddListener( EEvent::RENDER, this, &ComponentCamera::OnRender );
		m_pOwner->GetScene()->GetWindowViewOwner()->AddListener( EEvent::WINDOW_VIEW_RESIZE, this, &ComponentCamera::OnWindowViewResize );
		m_pOwner->GetScene()->GetWindowViewOwner()->AddListener( EEvent::WINDOW_VIEW_DESTROY, this, &ComponentCamera::OnWindowViewDestroy );

		// Get RenderPath
		m_renderPath = Engine::GetInstance()->GetRenderer()->GetRenderPath();

		if ( m_renderPath == PSX_NULL )
		{
			PSX_PushError( "No render path set in renderer." );
			return EErrorCode::GRAPHICS;
		}

		EErrorCode::Type retCode = m_renderPath->CreateCameraSettings( this, &m_pRenderPathSettings );

		if ( retCode != EErrorCode::OKAY )
		{
			PSX_PushError( "Failed to create CameraRenderPathSettings." );
			return retCode;
		}

		retCode = m_pRenderPathSettings->UpdateBufferSizes();

		//if ( retCode != EErrorCode::OKAY )
		//{
		//	PSX_PushError( "Failed to create update CameraRenderPathSettings." );
		//	return retCode;
		//}

		return EErrorCode::OKAY; 
	}


	void ComponentCamera::Update( FLOAT dt )
	{
		m_view = *m_pWorldTransform;
		//PSX_PushLog1( "Z: %f", m_pWorldTransform->_43 );

		// Inverse of view matrix
		const Vector3 vecRight		= m_view.GetRight();
		const Vector3 vecUp			= m_view.GetUp();
		const Vector3 vecForward	= m_view.GetForward();
		const Vector3 position		= m_view.GetTranslation();

		const Vector3 invTranslation(
			-position.DotProduct( vecRight ),
			-position.DotProduct( vecUp ),
			-position.DotProduct( vecForward )
			);

		//m_view.Invert();
		m_view._11 = vecRight.x;		m_view._12 = vecUp.x;			m_view._13 = vecForward.x;		m_view._14 = 0.0f;
		m_view._21 = vecRight.y;		m_view._22 = vecUp.y;			m_view._23 = vecForward.y;		m_view._24 = 0.0f;
		m_view._31 = vecRight.z;		m_view._32 = vecUp.z;			m_view._33 = vecForward.z;		m_view._34 = 0.0f;
		m_view._41 = invTranslation.x;	m_view._42 = invTranslation.y;	m_view._43 = invTranslation.z;	m_view._44 = 1.0f;
	
		// Update the rest of the render parameter bidners
		m_viewProj = m_view * m_proj;
		m_invViewProj = m_viewProj; m_invViewProj.Invert();
		m_pParamPosWS->SetAsVector3( &GetOwnerUnsafe()->GetTransformWorld()->GetTranslation() );
	}

	void ComponentCamera::SetActive( BOOL bActive )
	{
		m_bActiveCamera = bActive; 
	
		if ( bActive )
		{
			// Set projection matrix
			//ParameterMatrix *pProj = Engine::GetInstance()->GetRenderer()->GetParameterSystem()->GetMatrix( GFX_PARAM_PROJECTION );
			//pProj->SetMatrix( &m_proj );
		}
	}

	void ComponentCamera::SetProjectionPerspective( FLOAT _near, FLOAT _far, FLOAT aspect, FLOAT fov )
	{
		m_projType = EProjection::Perspective;

		m_near = _near;
		m_far = _far;
		m_aspect = aspect;
		m_fov = fov;

		m_proj.BuildPerspectiveProjection( m_fov, m_aspect, m_near, m_far );
	
		BuildProjectionParameters();
	}

	void ComponentCamera::SetProjectionOrthogonal( UINT width, UINT height, FLOAT _near, FLOAT _far )
	{
		m_projType = EProjection::Orthogonal;

		m_width = width;
		m_height = height;
		m_near = _near;
		m_far = _far;

		m_proj.BuildOrthogonalProjection( (FLOAT)m_width, (FLOAT)m_height, m_near, m_far );

		BuildProjectionParameters();
	}

	void ComponentCamera::Render( Scene *pScene )
	{
		RenderView *pView = GetRenderView();

		// Give our window view a chance to pass render entries as well
		GetOwnerUnsafe()->GetScene()->GetWindowViewOwner()->OnSceneRender( pView );
		
		// Finally render the scene
		pScene->Render( this, pView );

		// TODO: We'll need to fix this for multiple render views
		//	 and render view order execution.
		m_renderPath->ExecuteRenderView( pView );
	}

	void ComponentCamera::BuildProjectionParameters( void ) const
	{
		// Build projections
		// Calculate our projection constants 
		// Source: http://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3/?blogsub=confirming#subscribe-blog
		// ProjectionA = FarClipDistance / (FarClipDistance - NearClipDistance);
		// ProjectionB = (-FarClipDistance * NearClipDistance) / (FarClipDistance - NearClipDistance);
		FLOAT projA = m_far / (m_far - m_near );
		FLOAT projB = (-m_far * m_near) / (m_far - m_near );

		ParameterSystem *pParamSys = Engine::GetInstance()->GetRenderer()->GetParameterSystem();
		ParameterScalar *pProjA = pParamSys->GetScalar( GFX_PARAM_CAM_PROJ_A );
		ParameterScalar *pProjB = pParamSys->GetScalar( GFX_PARAM_CAM_PROJ_B );

		// Set new projection values
		m_pREBindParams->FindParameterBinder( pProjA )->SetAsScalar( projA );
		m_pREBindParams->FindParameterBinder( pProjB )->SetAsScalar( projB );

		//// No need for binders. This will remain constant until the next time the projection
		////	parameters are explicitly changed.
		//pProjA->SetAsFLOAT( projA );
		//pProjB->SetAsFLOAT( projB );

		m_pParamClipNear->SetAsScalar( m_near );
		m_pParamClipFar->SetAsScalar( m_far );
	}

	void ComponentCamera::OnRender( IEvent *pEvent )
	{
		if ( pEvent->GetEventType() == EEvent::RENDER )
		{
			EventRender *pRenderEvent = (EventRender*)pEvent;
			RenderView *pView = pRenderEvent->GetRenderView();
			
			// The "active" camera is the camera's render path's 
			//	RenderView being currently used
			if ( pView == &m_renderView )
			{
				//pView->AddRenderEntry( &m_entryClearBuffer );

				pView->AddRenderEntry( m_pREBindParams );

				m_pRenderPathSettings->OnRender( pView );
			}
			else
			{

			}
		}
	}

	void ComponentCamera::OnTransform( IEvent *pEvent )
	{
		EventTransform *pTransform = (EventTransform*)pEvent;

		if ( pTransform->GetEventOwner() != this )
		{

			//m_pModel->SetTransform( pTransform->GetTransform() );
		}
	}

	void ComponentCamera::OnWindowViewResize( IEvent *pEvent )
	{
		m_pRenderPathSettings->UpdateBufferSizes();
	}

	void ComponentCamera::OnWindowViewDestroy( IEvent *pEvent )
	{
		// This crashes... Just don't remove listener when we clean this component up...
		// m_pOwner->GetScene()->GetWindowViewOwner()->RemoveListener( EEvent::WINDOW_VIEW_RESIZE, this, &ComponentCamera::OnWindowViewResize );
		// m_pOwner->GetScene()->GetWindowViewOwner()->RemoveListener( EEvent::WINDOW_VIEW_DESTROY, this, &ComponentCamera::OnWindowViewDestroy );

		m_bWindowViewExist = FALSE;
	}


}